package model;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * @author      Robert Rill, Zalan Czirjak 
 * @version     2011.0603
 * @since       1.6
 */

public class Matrix3D implements Matrix {
	private int size;
	private int [][][] matrix;
	
	public Matrix3D(int size) {
		this.size = size;
		matrix = new int[size][size][size];
	}
	
	public Matrix3D sum(Matrix3D a) throws MatrixException {
		Matrix3D c = new Matrix3D(size);
		if (a.getSize() != this.getSize()) {
			throw new MatrixException("Error in sum! Sizes of matrices do not match!");
		}
		for (int i = 0; i < size; ++i)
			for (int j = 0; j < size; ++j)
				for (int k = 0; k < size; ++k)
					c.setElement(a.getElement(i, j, k) + this.getElement(i, j, k), i, j, k); 
		return c;
	}
	
	public Matrix3D difference(Matrix3D a) throws MatrixException {
		Matrix3D c = new Matrix3D(size);
		if (a.getSize() != this.getSize()) {
			throw new MatrixException("Error in difference! Sizes of matrices do not match!");
		}
		for (int i = 0; i < size; ++i)
			for (int j = 0; j < size; ++j)
				for (int k = 0; k < size; ++k)
					c.setElement(a.getElement(i, j, k) - this.getElement(i, j, k), i, j, k); 
		return c;
	}
	
	public void printMatrix() {
		for (int k = 0; k < size; ++k) {
			for (int i = 0; i < size; ++i) {				
				 for (int j = 0; j < size; ++j)
					System.out.print(getElement(i,j,k) + " ");
				System.out.println();
			}
			System.out.println();
		}
		System.out.println("-------------------");
	}
	
	public void readCubeFromFile(String fileName) throws MatrixException {
		try {
			FileReader input = new FileReader(fileName);
			BufferedReader in = new BufferedReader(input);
			String s  = in.readLine();
			int n = Integer.parseInt(s);
			if (size != n)
				throw new MatrixException("Error reading from file: size in file doesn't match with matrix size!");
			int i = 0, j = 0;
			for (int k = 0; k < size; ++k) {
				in.readLine();
				for (i = 0; i < size; ++i) {
					s = in.readLine();
					String [] array = s.split(" ");
					if (array.length != size)
						throw new MatrixException("Error reading from file: line length doesn't match with matrix size!");
					for (j = 0; j < size; ++j) {
						n = Integer.parseInt(array[j]);
						this.setElement(n, i, j, k);
					}
				}
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**Kocka kiirasa file-ba.
	 * String fileName - forrasfile relativ eleresi utvonala*/
	public void writeCubeToFile(String fileName) {
		try {
			FileWriter output = new FileWriter(fileName);
			BufferedWriter out = new BufferedWriter(output);
			out.write(Integer.toString(size));	//elso sor kocka merete + ures sor
			out.newLine(); out.newLine();
			for (int k = 0; k < size; ++k) {
				for (int i = 0; i < size; ++i) {				
					 for (int j = 0; j < size; ++j)
						out.write(Integer.toString(getElement(i,j,k))+" ");	//<space>-el elvalasztva matrix formaban az elemek kiirasa
					out.newLine();
				}
				out.newLine();
			}
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}
	/**
	 * Visszateriti a lefoglalt kicsi kockak (matrix elemek) szamat.
	 * @return - lefoglalt matrixelemek szama
	 */
	public int numLitCubes() {
		int litCubes = 0;
		for (int k = 0; k < size; ++k)
			for (int i = 0; i < size; ++i) 				
				 for (int j = 0; j < size; ++j)
					 if(matrix[i][j][k]!=0)
						 litCubes++;
		return litCubes;
	}
	
	/**
	 * Visszateriti a lefoglalt elemek minimalis szintejet.
	 * 
	 * Szint alatt itt a kovetkezot ertem: Minden tengely szerint megnezzuk, hogy mekkora
	 * milyen hosszusagban helyezkednek el a lefoglalt elemek. Ezek kozul a minimum a megegyezik
	 * a szint ertekevel. 
	 * @return - szint erteke.
	 */
	public int getLevel(){
		int minI = size, minJ = size, minK = size;
		int maxI = 0, maxJ = 0, maxK = 0;
		for (int i = 0; i < size; ++i)
			for (int j = 0; j < size; ++j)
				for (int k = 0; k < size; ++k)
					if (this.getElement(i, j, k) != 0) {
						if (i<minI) minI = i;
						if (i>maxI) maxI = i;
						if (j<minJ) minJ = j;
						if (j>maxJ) maxJ = j;
						if (k<minK) minK = k;
						if (k>maxK) maxK = k;
					}
		int level=maxI - minI+1;
		if (maxJ - minJ+1  <level) level=maxJ - minJ+1;
		if (maxK - minK+1 <level) level=maxK - minK+1;	
		return level;
	}

	
	public int getElement(int i, int j, int k) {
		return matrix[i][j][k];
	}
	
	public void setElement(int number, int i, int j, int k) {
		matrix[i][j][k] = number;
	}	
	
	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public int[][][] getMatrix() {
		return matrix;
	}

	public void setMatrix(int[][][] matrix) {
		this.matrix = matrix;
	}
	
	/**
	 * Inicializalja a 3dMatrixot 0-kal.
	*/
	public void init(){
		for (int i = 0; i < size; ++i)
			for (int j = 0; j < size; ++j)
				for (int k = 0; k < size; ++k)
					setElement(0,i,j,k);
	}
	
	/**
	 * Megszamolja, hogy hany le nem foglalt hely van a matrixban.
	 * @return - a le nem foglalt elemek szama.
	 */
	public int emptySpace(){
		int num=0;
		for (int i = 0; i < size; ++i)
			for (int j = 0; j < size; ++j)
				for (int k = 0; k < size; ++k)
					if(getElement(i,j,k)==0)
						num++;
		return num;
	}

	/**
	 * Osszead ket Matrix3D-t.
	 * A parameterkent megadott matrixot hozzaadja a jelenlegi matrixhoz.
	 * @param m - hozzaadando matrix
	 * @return - true ha az osszeadas sikeres volt, kulonben false.
	 */
	public boolean canAddMatrix(Matrix3D m){
		if (m.getSize() != this.getSize())
			return false;	//ha meretek kulonboznek ... akkor nincs osszeadas
		for (int i = 0; i < size; ++i)
			for (int j = 0; j < size; ++j)
				for (int k = 0; k < size; ++k)
					if (1 < m.getElement(i, j, k) + this.getElement(i, j, k))
						return false;
		return true;
	}
	
}
